#if defined(USBCON) && defined(USBD_USE_CDC)

#include "USBSerial.h"
#include "usbd_cdc_if.h"
#include "wiring.h"

extern __IO uint32_t lineState;

USBSerial SerialUSB;
void serialEventUSB() __attribute__((weak));

void USBSerial::begin(void)
{
}

void USBSerial::begin(uint32_t)
{
}

void USBSerial::begin(uint32_t, uint8_t)
{
}

void USBSerial::end(void)
{
}

int USBSerial::available(void)
{
    return static_cast<int>(CDC_ReceiveQueue_ReadSize(&ReceiveQueue));
}

int USBSerial::availableForWrite(void)
{
    return static_cast<int>(CDC_TransmitQueue_WriteSize(&TransmitQueue));
}

int USBSerial::peek(void)
{
    return CDC_ReceiveQueue_Peek(&ReceiveQueue);
}

int USBSerial::read(void)
{
    auto ch = CDC_ReceiveQueue_Dequeue(&ReceiveQueue);
    return ch;
}

size_t USBSerial::readBytes(char *buffer, size_t length)
{
    uint16_t read;
    auto rest = static_cast<uint16_t>(length);
    _startMillis = millis();

    do
    {
        read = CDC_ReceiveQueue_Read(&ReceiveQueue, reinterpret_cast<uint8_t *>(buffer), rest);
        // CDC_resume_receive();
        rest -= read;
        buffer += read;
        if (rest == 0)
        {
            return length;
        }
    } while (millis() - _startMillis < _timeout);
    return length - rest;
}

size_t USBSerial::readBytesUntil(char terminator, char *buffer, size_t length)
{
    uint16_t read;
    auto rest = static_cast<uint16_t>(length);
    _startMillis = millis();
    do
    {
        bool found = CDC_ReceiveQueue_ReadUntil(&ReceiveQueue, static_cast<uint8_t>(terminator),
                                                reinterpret_cast<uint8_t *>(buffer), rest, &read);
        // CDC_resume_receive();
        rest -= read;
        buffer += read;
        if (found)
        {
            return length - rest;
        }
        if (rest == 0)
        {
            return length;
        }
    } while (millis() - _startMillis < _timeout);
    return length - rest;
}

void USBSerial::flush(void)
{
    while (CDC_TransmitQueue_ReadSize(&TransmitQueue) > 0)
    {
    }
}

size_t USBSerial::write(uint8_t ch)
{
    return write(&ch, 1);
}

size_t USBSerial::write(const uint8_t *buffer, size_t size)
{
    if (lineState == 0)
        return 0;

    size_t rest = size;
    while (rest > 0)
    {
        auto portion = (size_t)CDC_TransmitQueue_WriteSize(&TransmitQueue);
        if (rest < portion)
        {
            portion = rest;
        }

        if (portion > 0)
        {
            CDC_TransmitQueue_Enqueue(&TransmitQueue, buffer, portion);
            rest -= portion;
            buffer += portion;
        }
    }
    
    CDC_continue_transmit();
    return size - rest;
}

USBSerial::operator bool(void)
{
    bool result = false;
    if (lineState == 1)
    {
        result = true;
    }
    delay(10);
    return result;
}

#endif // USBCON && USBD_USE_CDC